package cachego

import (
	"context"
	"time"

	"github.com/FishGoddess/cachego/pkg/task"
)

const (
	// NoTTL means a key is never expired.
	NoTTL = 0
)

var (
	newCaches = map[CacheType]func(conf *config) Cache{
		standard: newStandardCache,
		lru:      newLRUCache,
		lfu:      newLFUCache,
	}
)

// Cache is the core interface of cachego.
// We provide some implements including standard cache and sharding cache.
type Cache interface {
	// Get gets the value of key from cache and returns value if found.
	// A nil value will be returned if key doesn't exist in cache.
	// Notice that we won't remove expired keys in get method, so you should remove them manually or set a limit of keys.
	// The reason why we won't remove expired keys in get method is for higher re-usability, because we often set a new value
	// of expired key after getting it (so we can reuse the memory of entry).
	Get(key string) (value interface{}, found bool)

	// Set sets key and value to cache with ttl and returns evicted value if exists.
	// See NoTTL if you want your key is never expired.
	Set(key string, value interface{}, ttl time.Duration) (evictedValue interface{})

	// Remove removes key and returns the removed value of key.
	// A nil value will be returned if key doesn't exist in cache.
	Remove(key string) (removedValue interface{})

	// Size returns the count of keys in cache.
	// The result may be different in different implements.
	Size() (size int)

	// GC cleans the expired keys in cache and returns the exact count cleaned.
	// The exact cleans depend on implements, however, all implements should have a limit of scanning.
	GC() (cleans int)

	// Reset resets cache to initial status which is like a new cache.
	Reset()

	// Load loads a key with ttl to cache and returns an error if failed.
	// We recommend you use this method to load missed keys to cache,
	// because it may use singleflight to reduce the times calling load function.
	Load(key string, ttl time.Duration, load func() (value interface{}, err error)) (value interface{}, err error)
}

func newCache(withReport bool, opts ...Option) (cache Cache, reporter *Reporter) {
	conf := newDefaultConfig()
	applyOptions(conf, opts)

	newCache, ok := newCaches[conf.cacheType]
	if !ok {
		panic("cachego: cache type doesn't exist")
	}

	if conf.shardings > 0 {
		cache = newShardingCache(conf, newCache)
	} else {
		cache = newCache(conf)
	}

	if withReport {
		cache, reporter = report(conf, cache)
	}

	if conf.gcDuration > 0 {
		RunGCTask(cache, conf.gcDuration)
	}

	return cache, reporter
}

// NewCache创建具有选项的缓存。
// 默认情况下，它将创建一个标准缓存，该缓存使用一个锁来解决数据竞争问题。
// 它可能会在高并发性下造成很大的性能问题。
// 您可以使用WithShardings创建有利于并发的分片缓存。
// 此外，您还可以使用选项为其他缓存（如lru）指定缓存类型。
// 如果你愿意，可以使用NewCacheWithReporter获取一个报告器。
func NewCache(opts ...Option) (cache Cache) {
	cache, _ = newCache(false, opts...)
	return cache
}

// NewCacheWithReport创建具有选项的缓存和报告器。
// 默认情况下，它将创建一个标准缓存，该缓存使用一个锁来解决数据竞争问题。
// 它可能会在高并发性下造成很大的性能问题。
// 您可以使用WithShardings创建有利于并发的分片缓存。
// 此外，您还可以使用选项为其他缓存（如lru）指定缓存类型。
func NewCacheWithReport(opts ...Option) (cache Cache, reporter *Reporter) {
	return newCache(true, opts...)
}

// RunGCTask在新的goroutine中运行gc任务，并返回cancel函数以取消该任务。
// 然而，在大多数情况下，您不需要手动调用它，相反，使用选项是一个更好的选择。
// 在某些情况下，将其设置为公共功能是为了进行更多的自定义。
// 例如，使用选项运行gc任务是不可取消的，因此您可以使用它自己运行gc任务
// 并获取取消函数以取消gc任务。
func RunGCTask(cache Cache, duration time.Duration) (cancel func()) {
	fn := func(ctx context.Context) {
		cache.GC()
	}

	ctx := context.Background()
	ctx, cancel = context.WithCancel(ctx)

	go task.New(fn).Context(ctx).Duration(duration).Run()
	return cancel
}
